﻿
using LightCAD.Core.Element3d;
using System;
using static System.Collections.Specialized.BitVector32;

namespace QdArch
{

    public class QdDoorAttribute : CptAttribute
    {
        public QdDoorAttribute(string category, string subCategorys) : base(category, subCategorys)
        {

        }

        public const string BuiltinUuid = "{0F3DE48D-5985-4145-BE21-831DC83A9AB1}";
        public override LcComponentDefinition GetBuiltinCpt(string subCategory)
        {
            return new QdDoorDef(BuiltinUuid, "内置", "门");
        }
    }

    [QdDoorAttribute("门", "平开门")]
    public class QdDoorDef : LcComponentDefinition
    {
        static QdDoorDef()
        {
            CptTypeParamsDef<QdDoorDef>.ParamsDef = new ParameterSetDefinition {
                new ParameterDefinition { DataType = LcDataType.String, Name = "MaterialId", DisplayName = "材质", },
            };
        }

        public QdDoorDef()
        {
            this.TypeParameters = new LcParameterSet(CptTypeParamsDef<QdDoorDef>.ParamsDef);
        }
        public QdDoorDef(string uuid, string name, string subCategory)
            : base(uuid, name, "门", "平开门", new LcParameterSet(CptTypeParamsDef<QdDoorDef>.ParamsDef))
        {
            this.Parameters = new ParameterSetDefinition()
            {
                new ParameterDefinition { DataType = LcDataType.Double, Name = "Thickness", DisplayName = "厚度", },
                new ParameterDefinition { DataType = LcDataType.Double, Name = "Width", DisplayName = "宽度", },
                new ParameterDefinition { DataType = LcDataType.Double, Name = "Height", DisplayName = "高度", },
                new ParameterDefinition { DataType = LcDataType.Double, Name = "Bottom", DisplayName= "底高度" } ,
                new ParameterDefinition { DataType = LcDataType.Bool, Name = "IsLeft", DisplayName = "是否左开", },
                new ParameterDefinition { DataType = LcDataType.Bool, Name = "IsNear", DisplayName = "是否在墙体左侧", },
                new ParameterDefinition { DataType = LcDataType.Double, Name = "Angle", DisplayName = "角度(0-90)", },
                new ParameterDefinition { DataType = LcDataType.Bool, Name = "IsSingle", DisplayName = "单扇/双扇", },
            };

            this.Curve2dProvider = new Curve2dProvider("平开门")
            {
                GetCurve2dsFunc = (p) =>
                {
                    ListEx<Curve2d> curves = null;

                    var isSingle = p.GetValue<bool>("IsSingle");
                    if (isSingle)
                    {
                        curves = GetShape2D_单扇平开门(p);
                    }
                    else
                    {
                        curves = GetShape2D_双扇平开门(p);
                    }
                    return new Curve2dGroupCollection() { new Curve2dGroup()
                        {
                            Curve2ds=curves
                        }};
                }
            };

            this.Solid3dProvider = new Solid3dProvider("平开门")
            {
                GetSolid3dsFunc = (p) =>
                {
                    var solid3d = new Solid3d();
                    return GetShape3D(p);
                }
            };
        }

        private ListEx<Curve2d> GetShape2D_单扇平开门(LcParameterSet pset)
        {
            var sc = new Shape2dCreator();

            var w = (double)pset["Width"];
            //var th = (double)pset["Thickness"];
            var isLeft = (bool)pset["IsLeft"];
            var isNear = (bool)pset["IsNear"];
            var angle = (double)pset["Angle"];

            var pL = sc.Point(-w / 2, 0);
            var pR = sc.Point(w / 2, 0);
            var pLNear = sc.RotatePoint(pL, pR, angle);
            var pLFar = sc.RotatePoint(pL, pR, -angle);
            var pRNear = sc.RotatePoint(pR, pL, -angle);
            var pRFar = sc.RotatePoint(pR, pL, angle);
            if (isLeft && isNear)
            {
                sc.DrawArc(pL.Clone(), w, -angle, 0);
                sc.DrawLine(pL.Clone(), pRNear.Clone());
            }
            else if (isLeft && !isNear)
            {
                sc.DrawArc(pL.Clone(), w, 0, +angle);
                sc.DrawLine(pL.Clone(), pRFar.Clone());
            }
            else if (!isLeft && isNear)
            {
                sc.DrawArc(pR.Clone(), w, 180, 180 + angle);
                sc.DrawLine(pR.Clone(), pLNear.Clone());
            }
            else if (!isLeft && !isNear)
            {
                sc.DrawArc(pR.Clone(), w, 180 - angle, 180);
                sc.DrawLine(pR.Clone(), pLFar.Clone());
            }

            return sc.Curves;
        }

        private ListEx<Curve2d> GetShape2D_双扇平开门(LcParameterSet pset)
        {
            var sc = new Shape2dCreator();

            var w = (double)pset["Width"];
            //var th = (double)pset["Thickness"];
            var isLeft = (bool)pset["IsLeft"];
            var isNear = (bool)pset["IsNear"];
            var angle = (double)pset["Angle"];

            var pC = sc.Point(0, 0);
            var pL = sc.Point(-w / 2, 0);
            var pR = sc.Point(w / 2, 0);
            var pCLNear = sc.RotatePoint(pC, pL, -angle);
            var pCLFar = sc.RotatePoint(pC, pL, angle);
            var pCRNear = sc.RotatePoint(pC, pR, angle);
            var pCRFar = sc.RotatePoint(pC, pR, -angle);
            if (isNear)
            {
                sc.DrawArc(pL.Clone(), w / 2, -angle, 0);
                sc.DrawLine(pL.Clone(), pCLNear.Clone());
                sc.DrawArc(pR.Clone(), w / 2, 180, 180 + angle);
                sc.DrawLine(pR.Clone(), pCRNear.Clone());
            }
            else
            {
                sc.DrawArc(pL.Clone(), w / 2, 0, angle);
                sc.DrawLine(pL.Clone(), pCLFar.Clone());
                sc.DrawArc(pR.Clone(), w / 2, 180 - angle, 180);
                sc.DrawLine(pR.Clone(), pCRFar.Clone());
            }

            return sc.Curves;
        }


        public ListEx<Curve2d> GetShape2D_中分电梯门(LcParameterSet pset)
        {
            var sc = new Shape2dCreator();

            const double gap = 25;
            const double offset = 30;
            const double recth = 10;
            var w = (double)pset["Width"];
            var th = (double)pset["Thickness"];
            //var isLeft = (bool)pset["IsLeft"];
            var isNear = (bool)pset["IsNear"];

            if (isNear)
            {
                var pL = sc.Point(-w / 2, -th / 2);
                var pR = sc.Point(w / 2, -th / 2);
                sc.DrawLine(pL.Clone(), pR.Clone());
                //left
                sc.DrawPolygon(
                    sc.Point(pL.X - offset, -th / 2 - gap),
                    sc.Point(-offset, -th / 2 - gap),
                    sc.Point(-offset, -th / 2 - gap - recth),
                    sc.Point(pL.X - offset, -th / 2 - gap - recth));
                //right
                sc.DrawPolygon(
                    sc.Point(pR.X + offset, -th / 2 - gap),
                    sc.Point(offset, -th / 2 - gap),
                    sc.Point(offset, -th / 2 - gap - recth),
                    sc.Point(pR.X + offset, -th / 2 - gap - recth));
            }
            else
            {
                var pL = sc.Point(-w / 2, th / 2);
                var pR = sc.Point(w / 2, th / 2);
                sc.DrawLine(pL.Clone(), pR.Clone());
                //left
                sc.DrawPolygon(
                    sc.Point(pL.X - offset, th / 2 + gap),
                    sc.Point(-offset, th / 2 + gap),
                    sc.Point(-offset, th / 2 + gap + recth),
                    sc.Point(pL.X - offset, th / 2 + gap + recth));
                //right
                sc.DrawPolygon(
                    sc.Point(pR.X + offset, th / 2 + gap),
                    sc.Point(offset, th / 2 + gap),
                    sc.Point(offset, th / 2 + gap + recth),
                    sc.Point(pR.X + offset, th / 2 + gap + recth));

            }

            return sc.Curves;
        }

        private Solid3dCollection GetShape3D(LcParameterSet pset)
        {
            var isLeft = (bool)pset["IsLeft"];
            var bottom = (double)pset["Bottom"];
            var width = (double)pset["Width"];
            var start = new Vector3(-width / 2, 0, bottom);
            var end = new Vector3(width / 2, 0, bottom);
            var height = (double)pset["Height"];
            var thickness = (double)pset["Thickness"];
            var thicknessHlf = thickness / 2;
            var lineVec = new Vector3().SubVectors(end, start);
            var len = lineVec.Length();
            var xAxis = lineVec.Clone().Normalize();
            var yAxis = VectorUtil.ZAxis.Clone();
            var zAxis = new Vector3().CrossVectors(xAxis, yAxis).Normalize();
            var coodMat = new Matrix4();
            coodMat.MakeBasis(xAxis, yAxis, zAxis);
            coodMat.SetPosition(start);
            var shape = ShapeUtil.Rect(0, 0, len, height);
            var handleLoc = isLeft ? len - 100 : 100;
            var handleShape = ShapeUtil.Hexagon(handleLoc, height / 2, 50);
            var doorGeoData = GeoUtil.GetStretchGeometryData(shape, coodMat, thicknessHlf, -thicknessHlf);
            var handleGeoData = GeoUtil.GetStretchGeometryData(handleShape, coodMat, thicknessHlf + 50, -thicknessHlf - 50);
            //var geoDoor = doorGeoData.GetBufferGeometry();
            //geoDoor.computeVertexNormals();
            //var geoHandle = handleGeoData.GetBufferGeometry();
            //geoHandle.computeVertexNormals();

            var solids = new Solid3dCollection()
            {
                new Solid3d()
                {
                    Geometry = new LightCAD.MathLib.GeometryData()
                    {
                        Verteics = doorGeoData.Position.ToArray(),
                        Indics = doorGeoData.Index.ToArray(),
                        Groups = new GeometryGroup[1]
                        {
                            new GeometryGroup{ Name = "Geometry", Start = 0, Count = doorGeoData.Index.Length, MaterialIndex = 0 },
                        }
                    }
                },
                new Solid3d()
                {
                    Geometry = new LightCAD.MathLib.GeometryData()
                    {
                        Verteics = handleGeoData.Position.ToArray(),
                        Indics = handleGeoData.Index.ToArray(),
                        Groups = new GeometryGroup[1]
                        {
                            new GeometryGroup{ Name = "Geometry", Start = 0, Count = handleGeoData.Index.Length, MaterialIndex = 0 },
                        }
                    }
                }
            };
            return solids;
        }
    }

    public class QdDoorInstance : LcComponentInstance
    {
        public QdDoorDef DoorDef { get => this.Definition as QdDoorDef; }

        /// <summary>
        /// 在墙上用中心插入
        /// </summary>
        public QdWall Wall { get; set; }

        public Vector2 StartPoint { get; set; }
        public Vector2 EndPoint { get; set; }
        public Vector2 ControlPoint { get; set; }

        public double Thickness
        {
            get
            {
                return Convert.ToDouble(this.Parameters["Thickness"]);
            }
            set
            {
                this.Parameters["Thickness"] = value;
            }
        }
        public double Width
        {
            get
            {
                return Convert.ToDouble(this.Parameters["Width"]);
            }
            set
            {
                this.Parameters["Width"] = value;
            }
        }
        public double Height
        {
            get
            {
                return Convert.ToDouble(this.Parameters["Height"]);
            }
            set
            {
                this.Parameters["Height"] = value;
            }
        }
        public double Bottom
        {
            get
            {
                return Convert.ToDouble(this.Parameters["Bottom"]);
            }
            set
            {
                this.Parameters["Bottom"] = value;
            }
        }

        public bool IsLeft
        {
            get
            {
                return Convert.ToBoolean(this.Parameters["IsLeft"]);
            }
            set
            {
                this.Parameters["IsLeft"] = value;
            }
        }
        public bool IsNear
        {
            get
            {
                return Convert.ToBoolean(this.Parameters["IsNear"]);
            }
            set
            {
                this.Parameters["IsNear"] = value;
            }
        }

        public QdDoorInstance(QdDoorDef doorRef) : base(doorRef)
        {

        }

        public QdDoorInstance(double thickness, double width, double height, double bottom, bool isLeft, bool isNear, double angle, bool isSingle, QdDoorDef doorRef) : base(doorRef)
        {
            this.Type = ArchElementType.Door;
            this.Parameters.SetValue("Thickness", thickness);
            this.Parameters.SetValue("Width", width);
            this.Parameters.SetValue("Height", height);
            this.Parameters.SetValue("Bottom", bottom);
            this.Parameters.SetValue("IsLeft", isLeft);
            this.Parameters.SetValue("IsNear", isNear);
            this.Parameters.SetValue("Angle", angle);
            this.Parameters.SetValue("IsSingle", isSingle);
        }

        public override Box2 GetBoundingBox()
        {
            if (this.Parameters == null || this.Parameters.Values.Length == 0)
            {
                return Box2.Empty;
            }

            var center = this.Position.ToVector2() - new Vector2(Width, Width);
            return new Box2(center, Width * 2, Width * 2);

        }

        public void ResetControlPoint()
        {
            var isRight = !this.IsLeft; //是否在插入点右侧
            var isUp = !this.IsNear; //是否在墙线上方，即墙线左侧

            //第三个点的旋转角度， 经观察发现三个点在半径相同的同一个圆上，圆心为doorRef.InsertPoint
            double angle = this.Wall.BaseLine.Dir.Angle();
            double angle45 = Math.PI / 4;
            if (isRight && isUp)
            {
                angle += angle45;
            }
            else if (!isRight && isUp)
            {
                angle += Math.PI / 2 + angle45;
            }
            else if (!isRight && !isUp)
            {
                angle += Math.PI + angle45;
            }
            else if (isRight && !isUp)
            {
                angle += Math.PI * 1.5 + angle45;
            }

            var lineDir = Vector2.RotateInRadian(new Vector2(1, 0), angle);
            var lineOffset = lineDir * (this.Width / 2);

            this.ControlPoint = this.Position.ToVector2() + lineOffset;
        }

        public override bool IntersectWithBox(Polygon2d testPoly, List<RefChildElement> intersectChildren = null)
        {
            return base.IntersectWithBox(testPoly, intersectChildren);
            Box2 thisBox = this.BoundingBox;
            if (!thisBox.IntersectsBox(testPoly.BoundingBox))
            {
                return false;
            }

            //var shape2d = this.Door.Shape2D;
            var wallLine = this.Wall.BaseLine;
            var angle = wallLine.Dir.Angle();

            var curves = this.Curves[0].Curve2ds;
            foreach (var curve in curves)
            {
                if (curve.Type == Curve2dType.Line2d)
                {
                    var line = curve as Line2d;
                    //var newLine = line.ApplyMatrix(this.Matrix);
                    var newLine = line;
                    if (GeoUtils.IsPolygonIntersectLine(testPoly.Points, newLine.Start, newLine.End)
                     || GeoUtils.IsPolygonContainsLine(testPoly.Points, newLine.Start, newLine.End))
                    {
                        return true;
                    }
                }
                else if (curve.Type == Curve2dType.Arc2d)
                {
                    var arc = curve as Arc2d;
                    var newArc = new Arc2d();
                    newArc.Radius = arc.Radius;
                    //newArc.Center = this.Matrix.MultiplyPoint(arc.Center);
                    newArc.Center = arc.Center;
                    newArc.StartAngle = arc.StartAngle + Utils.RadianToDegree(angle);
                    newArc.EndAngle = arc.EndAngle + Utils.RadianToDegree(angle);

                    if (GeoUtils.IsPolygonIntersectArc(testPoly.Points, newArc))
                    {
                        return true;
                    }
                }
            }
            return false;
        }
        public override bool IncludedByBox(Polygon2d testPoly, List<RefChildElement> includedChildren = null)
        {
            return base.IncludedByBox(testPoly, includedChildren);
            Box2 thisBox = this.BoundingBox;
            if (!testPoly.BoundingBox.ContainsBox(thisBox))
            {
                return false;
            }
            return true;
        }

        public override void OnRemoveAfter()
        {
            this.Wall.RemoveLcComponent(this, "Door", AssociateType.Cross);
        }

        public override LcElement Clone()
        {
            var clone = new QdDoorInstance(this.DoorDef);
            clone.Copy(this);
            clone.Initilize(this.Document);
            return clone;
        }

        public override void Copy(LcElement src)
        {
            base.Copy(src);
            var doorRef = (QdDoorInstance)src;
            //var pset = QdDoorDef.ParamsDefinition.New();
            //pset["Thickness"] = doorRef.Parameters["Thickness"];
            //pset["Width"] = doorRef.Parameters["Width"];
            //pset["Height"] = doorRef.Parameters["Height"];
            //pset["Bottom"] = doorRef.Parameters["Bottom"];
            //pset["IsLeft"] = doorRef.Parameters["IsLeft"];
            //pset["IsNear"] = doorRef.Parameters["IsNear"];
            //this.Parameters = pset;
            this.Wall = doorRef.Wall;
            this.StartPoint = doorRef.StartPoint.Clone();
            this.EndPoint = doorRef.EndPoint.Clone();
            this.ControlPoint = doorRef.ControlPoint.Clone();
            this.Thickness = doorRef.Thickness;
            this.Width = doorRef.Width;
            this.Height = doorRef.Height;
            this.Bottom = doorRef.Bottom;
            this.IsNear = doorRef.IsNear;
            this.IsLeft = doorRef.IsLeft;
        }

        public override void ReadObjectRefs<T>(ICollection<T> objs)
        {
            if (this.Wall != null)
            {
                var wallId = this.Wall.Id;
                this.Wall = objs.FirstOrDefault(o => o.Id == wallId) as QdWall;
            }
            if (this.Wall != null && this.DoorDef != null)
            {
                this.Wall.AddLcComponent(this, "Door", AssociateType.Cross);
            }
        }

        public override void WriteProperties(Utf8JsonWriter writer, JsonSerializerOptions soptions)
        {
            base.WriteProperties(writer, soptions);

            writer.WriteString("DoorId", this.DoorDef.Uuid);
            writer.WriteNumberProperty("WallId", this.Wall.Id);
            writer.WriteVector2dProperty(nameof(this.StartPoint), this.StartPoint);
            writer.WriteVector2dProperty(nameof(this.EndPoint), this.EndPoint);
            writer.WriteVector2dProperty(nameof(this.ControlPoint), this.ControlPoint);

            if (this.Thickness != 0)
            {
                writer.WriteNumberProperty(nameof(Thickness), this.Thickness);
            }
            if (this.Width != 0)
            {
                writer.WriteNumberProperty(nameof(Width), this.Width);
            }
            if (this.Height != 0)
            {
                writer.WriteNumberProperty(nameof(Height), this.Height);
            }
            if (this.Bottom != 0)
            {
                writer.WriteNumberProperty(nameof(Bottom), this.Bottom);
            }

            if (this.IsLeft)
            {
                writer.WriteBoolProperty(nameof(IsLeft), this.IsLeft);
            }

            if (this.IsNear)
            {
                writer.WriteBoolProperty(nameof(IsNear), this.IsNear);
            }

        }

        public override void ReadProperties(ref JsonElement jele)
        {
            base.ReadProperties(ref jele);
            var doorId = jele.ReadStringProperty("DoorId");
            var cpt = this.Document.Components[doorId];
            var door = new QdDoorDef();
            door.Uuid = doorId;
            this.Definition = door;
            this.Document.Components.Remove(doorId);
            this.Document.Components.Add(door);
            //this.Wall = new QdWall()
            //{
            //    Id = jele.ReadIdProperty("WallId")
            //};
            //this.Parameters = QdDoorDef.ParamsDefinition.New();
            this.StartPoint = jele.ReadVector2dProperty(nameof(this.StartPoint));
            this.EndPoint = jele.ReadVector2dProperty(nameof(this.EndPoint));
            this.ControlPoint = jele.ReadVector2dProperty(nameof(this.ControlPoint));
            this.Thickness = jele.ReadDoubleProperty(nameof(Thickness));
            this.Width = jele.ReadDoubleProperty(nameof(Width));
            this.Height = jele.ReadDoubleProperty(nameof(Height));
            this.Bottom = jele.ReadDoubleProperty(nameof(Bottom));
            this.IsLeft = jele.ReadBoolProperty(nameof(IsLeft)) ?? this.IsLeft;
            this.IsNear = jele.ReadBoolProperty(nameof(IsNear)) ?? this.IsNear;
        }
    }


}